package config

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"io"
	"os"
	"strings"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/logging"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/core"
	"github.com/pkg/errors"
	"github.com/spf13/viper"
)

var logModules = [...]string{"ichainsdk", "ichainsdk/client", "ichainsdk/core", "ichainsdk/ichain", "ichainsdk/common",
	"ichainsdk/iam", "ichainsdk/util", "ichainsdk/context"}

type options struct {
	envPrefix    string
	templatePath string
}

const (
	cmdRoot   = "ICHAIN_SDK"
	InvalidId = "invalid"
)

type Option func(opts *options) error

// FromReader 读取配置 configtype为yaml或者json
func FromReader(in io.Reader, configType string, opts ...Option) core.ConfigProvider {
	id := InvalidId
	buf := new(bytes.Buffer)
	_, err := buf.ReadFrom(in)
	if err == nil {
		id = configId(buf.Bytes())
	}

	return core.ConfigProvider{
		Id: id,
		Func: func() ([]core.ConfigBackend, error) {
			return initFromReader(in, configType, opts...)
		},
	}
}

// FromFile 在文件中读取配置
func FromFile(name string, opts ...Option) core.ConfigProvider {
	var sdkId = InvalidId
	f, err := os.Open(name)
	if err == nil {
		defer f.Close()
		buff := new(bytes.Buffer)
		_, err = buff.ReadFrom(f)
		if err == nil {
			sdkId = configId(buff.Bytes())
		}
	}

	return core.ConfigProvider{
		Id: sdkId,
		Func: func() ([]core.ConfigBackend, error) {
			backend, err := newBackend(opts...)
			if err != nil {
				return nil, err
			}

			if name == "" {
				return nil, errors.New("filename is required")
			}

			backend.configViper.SetConfigFile(name)

			err = backend.configViper.MergeInConfig()
			if err != nil {
				return nil, errors.Wrapf(err, "loading config file failed: %s", name)
			}

			setLogLevel(backend)

			return []core.ConfigBackend{backend}, nil
		},
	}
}

func configId(cfg []byte) string {
	sh := sha256.New()
	sh.Write(cfg)
	h := sh.Sum(nil)
	return base64.StdEncoding.EncodeToString(h)
}

// FromRaw 在字节码中读取
func FromRaw(configBytes []byte, configType string, opts ...Option) core.ConfigProvider {
	sdkID := configId(configBytes)
	return core.ConfigProvider{
		Id: sdkID,
		Func: func() ([]core.ConfigBackend, error) {
			buf := bytes.NewBuffer(configBytes)
			return initFromReader(buf, configType, opts...)
		},
	}
}

// FromBuilder 直接用代码构建配置
func FromBuilder(builder *Builder, opts ...Option) core.ConfigProvider {
	return FromRaw(builder.Bytes(), builder.Type(), opts...)
}

func initFromReader(in io.Reader, configType string, opts ...Option) ([]core.ConfigBackend, error) {
	backend, err := newBackend(opts...)
	if err != nil {
		return nil, err
	}

	if configType == "" {
		return nil, errors.New("empty config type")
	}

	backend.configViper.SetConfigType(configType)
	err = backend.configViper.MergeConfig(in)
	if err != nil {
		return nil, err
	}
	setLogLevel(backend)

	return []core.ConfigBackend{backend}, nil
}

// WithEnvPrefix 环境变量前缀
func WithEnvPrefix(prefix string) Option {
	return func(opts *options) error {
		opts.envPrefix = prefix
		return nil
	}
}

func newBackend(opts ...Option) (*defConfigBackend, error) {
	o := options{
		envPrefix: cmdRoot,
	}

	for _, option := range opts {
		err := option(&o)
		if err != nil {
			return nil, errors.WithMessage(err, "Error in options passed to create new config backend")
		}
	}

	v := newViper(o.envPrefix)

	backend := &defConfigBackend{
		configViper: v,
		opts:        o,
	}

	err := backend.loadTemplateConfig()
	if err != nil {
		return nil, err
	}

	return backend, nil
}

func newViper(cmdRootPrefix string) *viper.Viper {
	myViper := viper.New()
	myViper.SetEnvPrefix(cmdRootPrefix)
	myViper.AutomaticEnv()
	replacer := strings.NewReplacer(".", "_")
	myViper.SetEnvKeyReplacer(replacer)
	return myViper
}

func setLogLevel(backend core.ConfigBackend) {
	loggingLevelString, _ := backend.Lookup("client.logging.level")
	logLevel := logging.INFO
	if loggingLevelString != nil {
		var err error
		logLevel, err = logging.LogLevel(loggingLevelString.(string))
		if err != nil {
			panic(err)
		}
	}

	for _, logModule := range logModules {
		logging.SetLevel(logModule, logLevel)
	}
}
